package robocup.component.speechacts;

import java.util.Arrays;
import java.util.LinkedList;

import robocup.component.geometry.Vektor;
import robocup.component.math.NumberConverter;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;

/**
 * This class provides methods for encoding and decoding of messages. Encoding
 * enables to create strings out of objects such as of a Player or the Ball.
 * Decoding works the other way round, whereas the first character of the string
 * is interpreted as message-id.<br>
 * The set of allowed characters is given in NumberConverter.digits, together
 * with some useful methods to transform decimal numbers into numbers of base
 * digits.length. For the purpose of better performance, some arrays containing
 * pre-calculated values are also given here.<br>
 * Note that for floating-point calculations, all values are normalized and
 * non-negative. This implies that positions have to encoded by angles of the
 * interval [0,360] normalized to [0,1] and with distances reaching to an upper
 * limit [0,70], also normalized. This works similar for other encodings.<br>
 * <br>
 * 
 * Use this class as follows:
 * <ol>
 * <li> For encoding, simply call the corresponding method to create a string
 * out of the given data. These strings can be given to a say-message directly.
 * You can also use more than one encoded string by concatenation, but be sure
 * that the length of the resulting string does not exceed the maximum number of
 * allowed characters.
 * <li> For decoding, use the method <code>generateMessages()</code> first,
 * which extracts all messages contained in a string and remembers their ids in
 * <code>this.messageTypes</code>.
 * <li> After that, iterate the message-types (<code>getMessageTypes()</code>
 * returns them), and if the actual message should be decoded, call the method
 * <code>decodeMessageNumber()</code> by giving the index of the type as
 * parameter. This will result in decoding the corresponding message, and sets
 * the specific attributes of this class.
 * <li> Use the getter methods (e.g. getSpeed) to access the decoded values.
 * </ol>
 * 
 * Note also that using this class should be done in a certain way. Do not
 * create multiple instances of this class, but use that instantiated in the
 * WorldModel (should be available in most classes). After decoding, the
 * resulting values (e.g. the speed of a player) is bound to an attribute of
 * this class (e.g. this.speed), and can be read by using the corresponding
 * getter-method. Be sure that the used decoding sets the specific values you
 * read afterwards, because this class doesn't check, which are actually filled.
 * Otherwise you might use old values, which do not belong to the message you
 * decoded last.
 */
public class MessageFactory {

  /**
   * Cycle in which the last messages was received.
   */
  private int                  bodyCycle        = -1;

  /**
   * This array stores the values of each character from <code>
   * NumberConverter.digits</code>,
   * whereas each value is located at the index of the characters integer value
   * (for fast access). Note that the characters are ordered by their int-value,
   * such that the last character, which is used to initialize the array, also
   * has the largest value.
   */
  public final int[]           valuesOfChars    = new int[((int) NumberConverter.digits[NumberConverter.digits.length - 1]) + 1];

  /**
   * This array stores the lengths of each message, which is identified by the
   * first character of the corresponding string. The lengths of the messages
   * are also accessed via the integer value of the id as index. Up to now, this
   * method allows to define digits.length different messages. If more are
   * required, some id's should introduce a following id, which on the other
   * hand reduces the available characters for encoding.
   */
  public final int[]           lengthOfMessage  = new int[((int) NumberConverter.digits[NumberConverter.digits.length - 1]) + 1];

  /**
   * This array contains precalculated values for the purpose of using the
   * information of one character for two information. The given array is used
   * to access the first (higher) value. An example could be the encoding of the
   * tricot number (11 different values) and the stamina in 4 different levels.
   * Since the former can be expressed using 4 bits and the latter using 2 bits,
   * they can share a character (little more than 6 bits) for the encoding. The
   * frontValue refers to the larger bits.
   */
  public final int[][]         sharedFrontValue = new int[((int) NumberConverter.digits[NumberConverter.digits.length - 1]) + 1][8];

  /**
   * This array contains precalculated values for the purpose of using the
   * information of one character for two information. The given array is used
   * to access the first (higher) value. An example could be the encoding of the
   * tricot number (11 different values) and the stamina in 4 different levels.
   * Since the former can be expressed using 4 bits and the latter using 2 bits,
   * they can share a character (little more than 6 bits) for the encoding. The
   * backValue refers to the lower bits.
   */
  public final int[][]         sharedBackValue  = new int[((int) NumberConverter.digits[NumberConverter.digits.length - 1]) + 1][8];

  /**
   * A string buffer for performant string concatenation. This is used for
   * creating the messages.
   */
  private StringBuffer         str              = new StringBuffer();

  /**
   * A list containing all currently received message types. The message factory
   * first retrieves all types before decoding all messages, which enables to
   * decode only those messages, which are currently needed.
   */
  private final LinkedList<MESSAGES> messageTypes     = new LinkedList<MESSAGES>();

  /**
   * A list containing the corresponding message contents for the <code>
   * messageTypes</code>.
   * These are created by splitting received messages.
   */
  private final LinkedList<String>   messageContent   = new LinkedList<String>();

  /**
   * Buffer-array for a single double value. Used with the class <code>
   * NumberConverter</code>
   * in order to transform doubles to a string and vice versa.
   */
  private final double[]       data_1           = new double[1];

  /**
   * Buffer-array for two double values. Used with the class <code>
   * NumberConverter</code>
   * in order to transform doubles to a string and vice versa, whereas this may
   * be used to transform two doubles into three characters in order to gain
   * higher precision.
   */
  private final double[]       data_2           = new double[2];

  /**
   * Buffer-array for a single character. Used with the class <code>
   * NumberConverter</code>
   * in order to transform characters to doubles and vice versa.
   */
  private final char[]         cdata_1          = new char[1];

  /**
   * Buffer-array for two characters. Used with the class <code>
   * NumberConverter</code>
   * in order to transform characters to doubles and vice versa.
   */
  private final char[]         cdata_2          = new char[2];

  /**
   * Buffer-array for three characters. Used with the class <code>
   * NumberConverter</code>
   * in order to transform characters to doubles and vice versa.
   */
  private final char[]         cdata_3          = new char[3];

  /**
   * Buffer-array for four characters. Used with the class <code>
   * NumberConverter</code>
   * in order to transform characters to doubles and vice versa.
   */
  private final char[]         cdata_4          = new char[4];

  /**
   * Maximum value for angles. These are encoded using the [0,360] interval,
   * because no negative values can be encoded.
   */
  private final double         maxAngle         = 360;

  /**
   * Maximum values for distances. Since all values with floating points are
   * normalized, a maximum value has to be given.
   */
  private final double         maxPosDist       = 70;

  /**
   * The maximum encoded speed of players.
   */
  private final double         maxPlayerSpdDist = 1.2;

  /**
   * The maximum encoded speed of the ball.
   */
  private final double         maxBallSpdDist   = 2.7;

  /**
   * The maximum encoded stamina value.
   */
  private final int            maxStamina       = 8000;

  /**
   * After decoding the message DRIBBLE_PPOS, the corresponding positions of the
   * players are stored in this array.
   */
  private final Vektor[]             positions        = { new Vektor(), new Vektor() };

  /**
   * After decoding the message DRIBBLE_PPOS, the corresponding positions of the
   * players are stored in this array.
   */
  private final int[]                numbers          = { -1, -1 };

  /**
   * After decoding any position (ball or player), the corresponding value is
   * stored in this Vektor.
   */
  private final Vektor               position         = new Vektor();

  /**
   * Every decoded speed is stored in this attribute.
   */
  private final Vektor               speed            = new Vektor();

  /**
   * A number of a player is stored in this attribute. Numbers reaching from 1
   * to 11 refers to teammates and from 12 to 22 to opponents.
   */
  private int                  number           = -1;

  /**
   * Each age of an encoded object (player or ball) is stored in this attribute.
   * Age-values reach from 0 to 2.
   */
  private int                  age              = -1;

  /**
   * Decoded stamina values are stored in this value.
   */
  private int                  stamina          = -1;

  /**
   * The decoded amount of cycles needed to intercept the ball is stored in this
   * attribute.
   */
  private int                  cycles           = 0;

  /**
   * Each decoded body-angle of a player is stored here.
   */
  private double               bodyAngle        = 0;

  /**
   * Each decoded recovery-value of a player is stored here. Note that this has
   * a minimum of 0.5 and a maximum of 1.
   */
  private double               recovery         = 1.0;

  /**
   * This array is used to store the decoded player types of the opponents.
   */
  private int[]                intArray;

  /**
   * Constructor - this class is able to encode and decode certain, well-defined
   * messages. It is optimized for performance, hence pre-calculations of
   * <code>valuesOfChars</code>, <code>sharedFrontValue
   * </code> and
   * <code>sharedBackValue</code> are done here. Additionally, the length of
   * all defined messages have to be set to <code>lengthOfMessage</code>. If
   * this class is extended to support new message types, their lengths have to
   * be added here as well.
   */
  public MessageFactory() {

    // init all to -1
    Arrays.fill(this.valuesOfChars, -1);
    Arrays.fill(this.lengthOfMessage, -1);
    for (final int[] a : this.sharedFrontValue) {
      Arrays.fill(a, -1);
    }
    for (final int[] a : this.sharedBackValue) {
      Arrays.fill(a, -1);
    }

    // fill data of helping arrays
    for (int i = 0; i < NumberConverter.digits.length; i++) {
      this.valuesOfChars[NumberConverter.digits[i]] = i;
      for (int j = 0; j < this.sharedFrontValue[i].length; j++) {
        this.sharedFrontValue[NumberConverter.digits[i]][j] = i / (j + 1);
        this.sharedBackValue[NumberConverter.digits[i]][j] = i % (j + 1);
      }
    }

    // fill data of message-length information
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.PLAYER_POS.ordinal()]] = 4;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.PLAYER_POS_STAMINA
        .ordinal()]] = 4;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.GOALIE_POS.ordinal()]] = 4;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.PLAYER_SPD.ordinal()]] = 4;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.BALL_POS.ordinal()]] = 4;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.BALL_SPD.ordinal()]] = 4;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_PLAYER_POS
        .ordinal()]] = 5;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_PLAYER_SPD
        .ordinal()]] = 5;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_BALL_POS
        .ordinal()]] = 5;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_BALL_SPD
        .ordinal()]] = 5;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.STAMINA.ordinal()]] = 1;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.RECOVERY.ordinal()]] = 3;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.MARK_PLAYER.ordinal()]] = 1;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.KILL_PLAYER.ordinal()]] = 1;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.COMPLETE_BALL
        .ordinal()]] = 9;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.KICKED_BALL.ordinal()]] = 9;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.DRIBBLE_BALL.ordinal()]] = 9;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.DRIBBLE_PPOS.ordinal()]] = 9;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.INTERCEPT_BALL
        .ordinal()]] = 9;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.COMPLETE_PLAYER
        .ordinal()]] = 9;
    this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_OPPTYPES
        .ordinal()]] = 11;
    // to be continued ...
  }

  /**
   * This enumeration contains all possible messages that can be sent and
   * understood by the agents. The Messages have a specific length, an id, which
   * is the first character, and a content. Some messages are only IDs (length
   * 1).
   */
  public enum MESSAGES {
    MESSAGE_1,
    MESSAGE_2,
    MESSAGE_3,
    MESSAGE_4,
    MESSAGE_5,
    MESSAGE_6,
    MESSAGE_7,
    MESSAGE_8,
    MESSAGE_9,
    MESSAGE_10,
    MESSAGE_11,
    MESSAGE_12,
    PLAYER_POS_STAMINA,
    COMPLETE_BALL,
    PLAYER_POS,
    PLAYER_SPD,
    BALL_POS,
    BALL_SPD,
    STAMINA,
    MARK_PLAYER,
    KILL_PLAYER,
    TRAINER_PLAYER_POS,
    TRAINER_PLAYER_SPD,
    TRAINER_BALL_POS,
    TRAINER_BALL_SPD,
    INTERCEPT_BALL,
    COMPLETE_PLAYER,
    KICKED_BALL,
    DRIBBLE_BALL,
    GOALIE_POS,
    RECOVERY,
    TRAINER_OPPTYPES,
    DRIBBLE_PPOS
  }

  /**
   * A Players Position is encoded in the following way: 1. The first character
   * contains the id as usual 2. The first character contains the type (which
   * player) and an age information (0,1,2). 3. The next three chars contain the
   * position, with an average error of 15cm. To reduce to 1cm, 4 chars should
   * be used!
   * 
   * @param player -
   *          the player, of which the position should be encoded
   * @param age -
   *          the age of the information (i.e. the time, how long the player
   *          wasn't seen)
   * @return String - the message representing the players position
   */
  public String encodePlayerPosition(final Player player,
      final int age) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = player.getPosition();
    int num = player.getNumber();
    if (num < 1) {
      return null;
    }
    if (num > 11) {
      return null;
    }
    if (!player.isFriend()) {
      num += 11;
    }

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.PLAYER_POS));

    // 2. player num and age
    this.str.append(NumberConverter.digits[(3 * num) + age]);

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = Math.min(angle / this.maxAngle, 1);
    this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
    this.str.append(NumberConverter.convertToChars(this.data_2, 3));
    return this.str.toString();
  }

  /**
   * This method encodes the player only in case, if it is the self player,
   * because then the sender is known (inside the message). Hence its number and
   * its age can be removed (because the number of the sender is tranmitted as
   * well, and the age is 0 per default), and stamina- information can be added.
   * 
   * @param player -
   *          the player, which should be self!
   * @return - the string containing the message
   */
  public String encodePlayerPositionAndStamina(final Player player) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = player.getPosition();
    final int num = player.getNumber();
    if (num < 1) {
      return null;
    }
    if (num > 11) {
      return null;
    }

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.PLAYER_POS_STAMINA));

    // 2. stamina: sender says only own stamina
    this.data_1[0] = player.getStamina() / (8000. + player.getPConf().EXTRA_STAMINA);
    if (this.data_1[0] > 1) {
      this.data_1[0] = 1;
    } else if (this.data_1[0] < 0) {
    	this.data_1[0] = 0;
    }
    this.str.append(NumberConverter.convertToChars(this.data_1, 1));

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = Math.min(angle / this.maxAngle, 1);
    this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
    this.str.append(NumberConverter.convertToChars(this.data_2, 3));
    return this.str.toString();
  }

  /**
   * A Goalies Position is encoded in the following way: 1. The first character
   * contains the id as usual 2. The first character contains the type (which
   * player) and an age information (0,1,2). 3. The next three chars contain the
   * position, with an average error of 15cm. To reduce to 1cm, 4 chars should
   * be used!
   * 
   * @param player -
   *          the goalie player
   * @param age -
   *          the age of this information (0, 1 or 2)
   * @return the string containing the message
   */
  public String encodeGoaliePosition(final Player player,
      final int age) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = player.getPosition();
    int num = player.getNumber();
    if (num < 1) {
      return null;
    }
    if (num > 11) {
      return null;
    }
    if (!player.isFriend()) {
      num += 11;
    }

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.GOALIE_POS));

    // 2. player num and age
    this.str.append(NumberConverter.digits[(3 * num) + age]);

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = Math.min(angle / this.maxAngle, 1);
    this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
    this.str.append(NumberConverter.convertToChars(this.data_2, 3));
    return this.str.toString();
  }

  /**
   * A Ball Position is encoded in the following way: 1. The first character
   * contains the id as usual 2. The first character contains the age
   * information (0, .. 10). 3. The next three chars contain the position, with
   * an average error of 15cm. To reduce to 1cm, 4 chars should be used!
   * 
   * @param the
   *          ball
   * @param the
   *          age of this information
   * @return the string containing the message
   */
  public String encodeBallPosition(final Ball ball,
      int age) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = ball.getPosition();
    age = Math.min(age, 10);

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.BALL_POS));

    // age
    this.str.append(NumberConverter.digits[age]);

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPosDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 3));

    return this.str.toString();
  }

  /**
   * This method encodes a ball with position, speed, and age
   * 
   * @param ball -
   *          the ball
   * @param age -
   *          the age of this information
   * @return the String representation
   */
  public String encodeCompleteBall(final Ball ball,
      int age) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor position = ball.getPosition();
    final Vektor speed = ball.getSpeed();
    age = Math.min(age, 20);

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.COMPLETE_BALL));

    // 2. age
    this.str.append(NumberConverter.digits[age]);

    // 3.-6. position
    double angle = Vektor.CENTER.getAngleTo(position);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    double dist = Vektor.CENTER.getDistance(position);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPosDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    // 7.-10. speed
    angle = speed.getAngle();
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    dist = speed.getLength();
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = Math.min(1, dist / this.maxBallSpdDist);
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));
    return this.str.toString();
  }

  /**
   * This method decodes a message which is generated with the
   * <code>encodeCompleteBall</code> method. The age is set to
   * <code>this.age</code>, the position and the speed are set to
   * <code>this.position</code> and <code>this.speed</code>.
   * 
   * @param str -
   *          the encoded string
   * @return true, if decoding was successful
   */
  public boolean decodeCompleteBall(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.COMPLETE_BALL, str.charAt(0))) {
      return false;
    }
    this.age = this.valuesOfChars[str.charAt(1)];

    // get position
    this.cdata_4[0] = str.charAt(2);
    this.cdata_4[1] = str.charAt(3);
    this.cdata_4[2] = str.charAt(4);
    this.cdata_4[3] = str.charAt(5);

    double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    double angle = result[0] * this.maxAngle;
    double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);

    // get speed
    this.cdata_4[0] = str.charAt(6);
    this.cdata_4[1] = str.charAt(7);
    this.cdata_4[2] = str.charAt(8);
    this.cdata_4[3] = str.charAt(9);

    result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    angle = result[0] * this.maxAngle;
    dist = result[1] * this.maxBallSpdDist;
    this.speed.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * This method encodes a balls speed and a players position, indicating that
   * the player is dribbling. The age indicates weather we dribble (next cycle)
   * or someone else (current cycle). Should reach maximal a value of 2!
   * 
   * @param ball -
   *          the ball
   * @param player -
   *          friend or enemy
   * @param age -
   *          [0,2]
   * @return the String representation
   */
  public String encodeDribbleBall(final Ball ball,
      final Player player,
      int age) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor position = ball.getPosition();
    Vektor speed = player.getSpeed();
    int number = player.getNumber();
    if (number < 1) {
      return null;
    }
    if (number > 11) {
      return null;
    }
    if (!player.isFriend()) {
      number += 11;
    }
    if (speed.getLength() < 0.1) {
      speed = new Vektor(Vektor.POLAR, 0.1, player.getBodyDir());
    }
    age = Math.max(0, age);
    age = Math.min(2, age);

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.DRIBBLE_BALL));
    // 2. player num and age
    this.str.append(NumberConverter.digits[(3 * number) + age]);

    // 3.-6. position
    double angle = Vektor.CENTER.getAngleTo(position);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    double dist = Vektor.CENTER.getDistance(position);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPosDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    // 7.-10. speed
    angle = speed.getAngle();
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    dist = speed.getLength();
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = Math.min(1, dist / this.maxPlayerSpdDist);
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));
    return this.str.toString();
  }

  /**
   * This method decodes the position of a player, and the speed of a ball,
   * which is used in the context of dribbling! The age is set as usual to
   * <code>this.age</code>, the position and the speed to <code>this.position
   * </code>
   * and <code>this.speed</code>.
   * 
   * @param str -
   *          the encoded string
   * @return true, if encoding was successful
   */
  public boolean decodeDribbleBall(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.DRIBBLE_BALL, str.charAt(0))) {
      return false;
    }

    // get number of player and age (for both):
    this.number = this.sharedFrontValue[str.charAt(1)][2];
    this.age = this.sharedBackValue[str.charAt(1)][2];

    // get position
    this.cdata_4[0] = str.charAt(2);
    this.cdata_4[1] = str.charAt(3);
    this.cdata_4[2] = str.charAt(4);
    this.cdata_4[3] = str.charAt(5);

    double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    double angle = result[0] * this.maxAngle;
    double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);

    // get speed
    this.cdata_4[0] = str.charAt(6);
    this.cdata_4[1] = str.charAt(7);
    this.cdata_4[2] = str.charAt(8);
    this.cdata_4[3] = str.charAt(9);

    result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    angle = result[0] * this.maxAngle;
    dist = result[1] * this.maxPlayerSpdDist;
    this.speed.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * This method encodes the positions of two players, whereas the age refers to
   * that of the first player.
   * 
   * @param player1 -
   *          friend or enemy
   * @param player2 -
   *          friend or enemy
   * @param age -
   *          [0,2]
   * @return the String representation
   */
  public String encodeDribblePlayer(final Player player1,
      final Player player2,
      int age) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor position1 = player1.getPosition();
    final Vektor position2 = player2.getPosition();
    int number1 = player1.getNumber();
    int number2 = player2.getNumber();
    if (number1 < 1) {
      return null;
    }
    if (number1 > 11) {
      return null;
    }
    if (!player1.isFriend()) {
      number1 += 11;
    }
    if (number2 < 1) {
      return null;
    }
    if (number2 > 11) {
      return null;
    }
    if (!player2.isFriend()) {
      number2 += 11;
    }

    age = Math.max(0, age);
    age = Math.min(2, age);

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.DRIBBLE_PPOS));
    // 2. player1 num and age
    this.str.append(NumberConverter.digits[(3 * number1) + age]);

    // 3.-6. position of player 1
    double angle = Vektor.CENTER.getAngleTo(position1);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    double dist = Vektor.CENTER.getDistance(position1);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPosDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    // 7. number of player 2
    this.str.append(NumberConverter.digits[number2]);

    // 8.-10. position of player 2
    angle = Vektor.CENTER.getAngleTo(position2);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    dist = Vektor.CENTER.getDistance(position2);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPosDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 3));
    return this.str.toString();
  }

  /**
   * This method decodes the position of two players which is used in the
   * context of dribbling! The age is set as usually to <code>this.age</code>,
   * the two positions to the corresponding elements of <code>this.positions
   * </code>.
   * The numbers of the players are stored in the corresponding elements of
   * <code>this.numbers</code>.
   * 
   * @param str -
   *          the encoded string for this message
   * @return true, if succeeds The positions are stored within the
   *         <positions>-array The numbers are stored within the <numbers>-array
   */
  public boolean decodeDribblePlayer(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.DRIBBLE_PPOS, str.charAt(0))) {
      return false;
    }

    // get number of player and age (for both):
    this.numbers[0] = this.sharedFrontValue[str.charAt(1)][2];
    this.age = this.sharedBackValue[str.charAt(1)][2];

    // get position of player 1
    this.cdata_4[0] = str.charAt(2);
    this.cdata_4[1] = str.charAt(3);
    this.cdata_4[2] = str.charAt(4);
    this.cdata_4[3] = str.charAt(5);

    double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    double angle = result[0] * this.maxAngle;
    double dist = result[1] * this.maxPosDist;
    this.positions[0].pointAtPolar(dist, angle);

    this.numbers[1] = this.valuesOfChars[str.charAt(6)];

    // get position of player 2
    this.cdata_3[0] = str.charAt(7);
    this.cdata_3[1] = str.charAt(8);
    this.cdata_3[2] = str.charAt(9);

    result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
    angle = result[0] * this.maxAngle;
    dist = result[1] * this.maxPosDist;
    this.positions[1].pointAtPolar(dist, angle);
    return true;
  }

  /**
   * This method encodes a ball with position and speed which results from a
   * kicked ball!
   * 
   * @param ball -
   *          the next ball, as it is expected after a kick
   * @return the String representation
   */
  public String encodeKickedBall(final Ball ball) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor position = ball.getPosition();
    final Vektor speed = ball.getSpeed();
    this.age = Math.min(this.age, 20);

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.KICKED_BALL));

    // 2. age, always 1
    this.str.append(NumberConverter.digits[1]);

    // 3.-6. position
    double angle = Vektor.CENTER.getAngleTo(position);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    double dist = Vektor.CENTER.getDistance(position);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPosDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    // 7.-10. speed
    angle = speed.getAngle();
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    dist = speed.getLength();
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = Math.min(1, dist / this.maxBallSpdDist);
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));
    return this.str.toString();
  }

  /**
   * This method decodes a message containing the new speed after a kick! The
   * age of the ball is set to <code>this.age</code>, its position and speed
   * as usual in <code>this.position</code> and <code>this.speed</code>.
   * 
   * @param str -
   *          the string-value!
   * @return true, if decoding was successful
   */
  public boolean decodeKickedBall(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.KICKED_BALL, str.charAt(0))) {
      return false;
    }
    this.age = this.valuesOfChars[str.charAt(1)];

    // get position
    this.cdata_4[0] = str.charAt(2);
    this.cdata_4[1] = str.charAt(3);
    this.cdata_4[2] = str.charAt(4);
    this.cdata_4[3] = str.charAt(5);

    double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    double angle = result[0] * this.maxAngle;
    double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);

    // get speed
    this.cdata_4[0] = str.charAt(6);
    this.cdata_4[1] = str.charAt(7);
    this.cdata_4[2] = str.charAt(8);
    this.cdata_4[3] = str.charAt(9);

    result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    angle = result[0] * this.maxAngle;
    dist = result[1] * this.maxBallSpdDist;
    this.speed.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * This method encodes a player with position, speed, and age.
   * 
   * @param player -
   *          the player to encode
   * @param age -
   *          the age of the player [0,1,2]
   * @return the String representation
   */
  public String encodeCompletePlayer(final Player player,
      int age) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor position = player.getPosition();
    final Vektor speed = player.getSpeed();
    final double bAngle = player.getBodyDir() + 180;
    int num = player.getNumber();
    if (num < 1) {
      return null;
    }
    if (num > 11) {
      return null;
    }
    if (!player.isFriend()) {
      num += 11;
    }
    age = Math.max(0, age);
    age = Math.min(2, age);

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.COMPLETE_PLAYER));

    // 2. player num and age
    this.str.append(NumberConverter.digits[(3 * num) + age]);

    // 3.-6. position
    double angle = Vektor.CENTER.getAngleTo(position);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    double dist = Vektor.CENTER.getDistance(position);
    this.data_2[0] = Math.min(angle / this.maxAngle, 1);
    this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    // 7.-9. speed
    angle = speed.getAngle();
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    dist = speed.getLength();
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = Math.min(1, dist / this.maxPlayerSpdDist);
    this.str.append(NumberConverter.convertToChars(this.data_2, 3));

    // 10. body angle
    this.data_1[0] = (bAngle / this.maxAngle);
    // System.out.println("B-ANGLE: " + data_1[0] + " " + bAngle);
    this.str.append(NumberConverter.convertToChars(this.data_1, 1));

    return this.str.toString();
  }

  /**
   * This method decodes the position of a player, i.e. his number, age, his
   * position and his speed as absolute vektor! The values are stored in
   * <code>this.age</code>, <code>this.number</code>,
   * <code>this.position</code> and <code>this.speed</code> respectively.
   * 
   * @param str -
   *          the encoded string-value!
   * @return true, if decoding was successful
   */
  public boolean decodeCompletePlayer(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.COMPLETE_PLAYER, str.charAt(0))) {
      return false;
    }

    // get number and age:
    this.number = this.sharedFrontValue[str.charAt(1)][2];
    this.age = this.sharedBackValue[str.charAt(1)][2];

    // get position
    this.cdata_4[0] = str.charAt(2);
    this.cdata_4[1] = str.charAt(3);
    this.cdata_4[2] = str.charAt(4);
    this.cdata_4[3] = str.charAt(5);

    double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    double angle = result[0] * this.maxAngle;
    double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);

    // get speed
    this.cdata_3[0] = str.charAt(6);
    this.cdata_3[1] = str.charAt(7);
    this.cdata_3[2] = str.charAt(8);

    result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
    angle = result[0] * this.maxAngle;
    dist = result[1] * this.maxPlayerSpdDist;
    this.speed.pointAtPolar(dist, angle);

    // get body angle
    this.cdata_1[0] = str.charAt(9);
    result = NumberConverter.convertToDoubles(this.cdata_1, 0, 1, 1);
    this.bodyAngle = (result[0] * this.maxAngle) - 180;

    return true;
  }

  /**
   * This method encodes the cycles that a player needs to intercept the ball,
   * together with the ball and its age.
   * 
   * @param ball -
   *          the ball
   * @param the
   *          age of the ball
   * @param cycles -
   *          the number of cycles to intercept the ball
   * @return the encoded string for this message
   */
  public String encodeInterceptBall(final Ball ball,
      int age,
      final int cycles) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor position = ball.getPosition();
    final Vektor speed = ball.getSpeed();
    age = Math.min(age, 20);

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.INTERCEPT_BALL));

    // 2. age
    this.str.append(NumberConverter.digits[age]);

    // 3. cycles
    this.str.append(NumberConverter.digits[cycles]);

    // 4.-7. position
    double angle = Vektor.CENTER.getAngleTo(position);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    double dist = Vektor.CENTER.getDistance(position);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPosDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    // 8.-10. speed
    angle = speed.getAngle();
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    dist = speed.getLength();
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = Math.min(1, dist / this.maxBallSpdDist);
    this.str.append(NumberConverter.convertToChars(this.data_2, 3));
    return this.str.toString();
  }

  /**
   * This method decodes a message, which was generated using <code>
   * encodeInterceptBall()</code>.
   * The age of the ball, the cycles for intercepting, the position and the
   * speed of the ball are set to <code>
   * this.age</code>,
   * <code>this.cycles</code>, <code>this.position</code> and
   * <code>this.speed</code> respectively.
   * 
   * @param the
   *          string representation of the message
   * @return true, if decoding was successful
   */
  public boolean decodeInterceptBall(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.INTERCEPT_BALL, str.charAt(0))) {
      return false;
    }
    this.age = this.valuesOfChars[str.charAt(1)];
    this.cycles = this.valuesOfChars[str.charAt(2)];
    // get position
    this.cdata_4[0] = str.charAt(3);
    this.cdata_4[1] = str.charAt(4);
    this.cdata_4[2] = str.charAt(5);
    this.cdata_4[3] = str.charAt(6);

    double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    double angle = result[0] * this.maxAngle;
    double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);

    // get speed
    this.cdata_3[0] = str.charAt(7);
    this.cdata_3[1] = str.charAt(8);
    this.cdata_3[2] = str.charAt(9);

    result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
    angle = result[0] * this.maxAngle;
    dist = result[1] * this.maxBallSpdDist;
    this.speed.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * A Players Position is encoded in the following way: 1. The first character
   * contains the id as usual 2. The first character contains the type (which
   * player) and an age information (0,1,2). 3. The next four chars contain the
   * position, with an average error of 1 cm!
   * 
   * @param player -
   *          the player, whose state is encoded
   * @return the string representation of the message
   */
  public String encodeTrainerPlayerPosition(final Player player) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = player.getPosition();
    int num = player.getNumber();
    if (num < 1) {
      return null;
    }
    if (num > 11) {
      return null;
    }
    if (!player.isFriend()) {
      num += 11;
    }
    final int age = 0;

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_PLAYER_POS));

    // 2. player num and age
    this.str.append(NumberConverter.digits[(3 * num) + age]);

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = Math.min(angle / this.maxAngle, 1);
    this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    return this.str.toString();
  }

  /**
   * A Ball Position is encoded in the following way: 1. The first character
   * contains the id as usual 2. The first character contains the type (0) and
   * an age information (0,1,2). 3. The next four chars contain the position,
   * with an average error of 1cm!
   * 
   * @param the
   *          ball
   * @return - the string representation of this message
   */
  public String encodeTrainerBallPosition(final Ball ball) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = ball.getPosition();
    final int age = 0;

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_BALL_POS));

    // 2. player num and age
    this.str.append(NumberConverter.digits[age]);

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPosDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    return this.str.toString();
  }

  /**
   * This message encodes the stamina of a player!
   * 
   * @param stam -
   *          the given stamina value!
   * @return the string representation!
   */
  public String encodeStamina(final int stam) {

    this.str = new StringBuffer();
    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.STAMINA));
    this.str.append(this.convertIntToCharacter(stam, 0, this.maxStamina));
    return this.str.toString();
  }

  /**
   * This message decodes the stamina value! The result is set to <code>
   * this.stamina</code>
   * 
   * @param str -
   *          the encoded stamina!
   * @return the stamina value
   */
  public boolean decodeStamina(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.STAMINA, str.charAt(0))) {
      return false;
    }
    this.cdata_1[0] = str.charAt(1);
    final double[] result = NumberConverter.convertToDoubles(this.cdata_1, 0, 1, 1);
    this.stamina = (int) (result[0] * this.maxStamina);

    return true;
  }

  /**
   * This message encodes the recovery of a player!
   * 
   * @param effort -
   *          the given recovery value!
   * @return the string representation!
   */
  public String encodeRecovery(final int num,
      final double rec) {

    this.str = new StringBuffer();
    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.RECOVERY));
    this.str.append(NumberConverter.digits[(3 * num)]);
    this.str.append(this.convertDoubleToCharsHP(rec, 0.5, 1.0));
    return this.str.toString();
  }

  /**
   * This message decodes the recovery value! The result is set to <code>
   * this.recovery</code>.
   * 
   * @param str -
   *          the encoded recovery!
   * @return the recovery value
   */
  public boolean decodeRecovery(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.RECOVERY, str.charAt(0))) {
      return false;
    }
    this.number = this.sharedFrontValue[str.charAt(1)][2];
    this.cdata_2[0] = str.charAt(2);
    this.cdata_2[1] = str.charAt(3);
    final double[] result = NumberConverter.convertToDoubles(this.cdata_2, 0, 2, 1);
    this.recovery = 0.5 + (result[0] * (1.0 - 0.5));

    return true;
  }

  /**
   * This message encodes the psotion of the BackFour-Line, either moving
   * forward of backward.
   * 
   * @return the string representation!
   */
  public String encodeMarkPlayer(final int oppnum) {

    if (oppnum > 11 || oppnum < 1) {
      return "";
    }

    this.str = new StringBuffer();
    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.MARK_PLAYER));

    // add data (1 chars)
    this.str.append(NumberConverter.digits[oppnum]);
    return this.str.toString();
  }

  /**
   * This message encodes the psotion of the BackFour-Line, either moving
   * forward of backward.
   * 
   * @return the string representation!
   */
  public boolean decodeMarkPlayer(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.MARK_PLAYER, str.charAt(0))) {
      return false;
    }
    this.number = this.valuesOfChars[str.charAt(1)];
    return true;
  }

  /**
   * This message encodes the psotion of the BackFour-Line, either moving
   * forward of backward.
   * 
   * @return the string representation!
   */
  public String encodeKillPlayer(final int num) {

    if (num > 22 || num < 1) {
      return "";
    }

    this.str = new StringBuffer();
    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.KILL_PLAYER));

    // add data (1 chars)
    this.str.append(NumberConverter.digits[num]);
    return this.str.toString();
  }

  /**
   * This message decodes a kill player message, received from the trainer. The
   * corresponding result is set to <code>this.number</code>, whereas values
   * from 12 to 22 refer to opponents.
   * 
   * @return true, if the decoding was successful
   */
  public boolean decodeKillPlayer(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.KILL_PLAYER, str.charAt(0))) {
      return false;
    }
    this.number = this.valuesOfChars[str.charAt(1)];
    return true;
  }

  /**
   * This method decodes the position of a player, i.e. his number, his age and
   * his position as absolute vektor! The results are set to <code>
   * this.number</code>,
   * <code>this.age</code> and <code>this.position</code>.
   * 
   * @param str -
   *          the string-value!
   * @return true, if the decoding was successful
   */
  public boolean decodePlayerPosString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.PLAYER_POS, str.charAt(0))) {
      return false;
    }

    // get number and age:
    this.number = this.sharedFrontValue[str.charAt(1)][2];
    this.age = this.sharedBackValue[str.charAt(1)][2];

    // get position
    this.cdata_3[0] = str.charAt(2);
    this.cdata_3[1] = str.charAt(3);
    this.cdata_3[2] = str.charAt(4);

    final double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * This method decodes a players position and his stamina. Note that the id of
   * the player is given in the info-type of this message, because this is
   * always the sender. The results are set to <code>this.stamina</code> and
   * </code>this.position</code>.
   * 
   * @param str -
   *          the string-value!
   * @return true, if decoding was successful
   */
  public boolean decodePlayerPosAndStaminaString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.PLAYER_POS_STAMINA, str.charAt(0))) {
      return false;
    }

    // get number and age:
    this.cdata_1[0] = str.charAt(1);
    double[] result = NumberConverter.convertToDoubles(this.cdata_1, 0, 1, 1);
    this.stamina = (int) (result[0] * 8000);

    // get position
    this.cdata_3[0] = str.charAt(2);
    this.cdata_3[1] = str.charAt(3);
    this.cdata_3[2] = str.charAt(4);

    result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * This method decodes the position of a goalie.The results are set to <code>
   * this.number</code>,
   * <code>this.age</code> and <code>this.position</code>.
   * 
   * @param str -
   *          the string-value!
   * @return true, if decoding was successful
   */
  public boolean decodeGoaliePosString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.GOALIE_POS, str.charAt(0))) {
      return false;
    }

    // get number and age:
    this.number = this.sharedFrontValue[str.charAt(1)][2];
    this.age = this.sharedBackValue[str.charAt(1)][2];

    // get position
    this.cdata_3[0] = str.charAt(2);
    this.cdata_3[1] = str.charAt(3);
    this.cdata_3[2] = str.charAt(4);

    final double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * This method decodes the position of the ball. The results are set to
   * <code>this.age</code> and <code>this.position</code>.
   * 
   * @param str -
   *          the string-value!
   * @return true, if decoding was successful
   */
  public boolean decodeBallPosString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.BALL_POS, str.charAt(0))) {
      return false;
    }
    // age & number
    // age = sharedBackValue[str.charAt(1)][2];
    // the ball only has age, no number
    this.age = this.valuesOfChars[str.charAt(1)];

    // get position
    this.cdata_3[0] = str.charAt(2);
    this.cdata_3[1] = str.charAt(3);
    this.cdata_3[2] = str.charAt(4);

    final double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * This method decodes the position of a player, which is given with a higher
   * precision. The results are set to <code>
   * this.number</code>,
   * <code>this.age</code> and <code>this.position</code>.
   * 
   * @param str -
   *          the string-value!
   * @return true, if the decoding was successful.
   */
  public boolean decodeTrainerPlayerPosString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.TRAINER_PLAYER_POS, str.charAt(0))) {
      return false;
    }

    // get number and age:
    this.number = this.sharedFrontValue[str.charAt(1)][2];
    this.age = this.sharedBackValue[str.charAt(1)][2];

    // get position
    this.cdata_4[0] = str.charAt(2);
    this.cdata_4[1] = str.charAt(3);
    this.cdata_4[2] = str.charAt(4);
    this.cdata_4[3] = str.charAt(5);

    final double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * This method decodes the position of the ball, which was encoded with higher
   * precision. The results are set to <code>this.age</code> and
   * <code>this.position</code>.
   * 
   * @param str -
   *          the string-value!
   * @return the position!
   */
  public boolean decodeTrainerBallPosString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.TRAINER_BALL_POS, str.charAt(0))) {
      return false;
    }
    // age & number
    this.age = this.sharedBackValue[str.charAt(1)][2];

    // get position
    this.cdata_4[0] = str.charAt(2);
    this.cdata_4[1] = str.charAt(3);
    this.cdata_4[2] = str.charAt(4);
    this.cdata_4[3] = str.charAt(5);

    final double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxPosDist;
    this.position.pointAtPolar(dist, angle);
    return true;
  }

  /**
   * A Players Speed is encoded in the following way: 1. The first character
   * contains the id as usual 2. The first character contains the type (which
   * player) and an age information (0,1,2). 3. The next three chars contain the
   * speed.
   * 
   * @param the
   *          player
   * @param age -
   *          the age of the information
   * @return the string representation
   */
  public String encodePlayerSpeed(final Player player,
      final int age) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = player.getSpeed();
    int num = player.getNumber();
    if (num < 1) {
      return null;
    }
    if (num > 11) {
      return null;
    }
    if (!player.isFriend()) {
      num += 11;
    }

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.PLAYER_SPD));

    // 2. player num and age
    this.str.append(NumberConverter.digits[(3 * num) + age]);

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPlayerSpdDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 3));

    return this.str.toString();
  }

  /**
   * A Ball Position is encoded in the following way: 1. The first character
   * contains the id as usual 2. The first character contains the type (0) and
   * an age information (0,1,2). 3. The next three chars contain the position,
   * with an average error of 15cm. To reduce to 1cm, 4 chars should be used!
   * 
   * @param the
   *          ball
   * @param age -
   *          the age of the onformation
   * @return - the string representation
   */
  public String encodeBallSpeed(final Ball ball,
      final int age) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = ball.getSpeed();

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.BALL_SPD));

    // 2. player num and age
    this.str.append(NumberConverter.digits[age]);

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = Math.min(dist / this.maxBallSpdDist, 1);
    this.str.append(NumberConverter.convertToChars(this.data_2, 3));

    return this.str.toString();
  }

  /**
   * A Players Speed is encoded in the following way: 1. The first character
   * contains the id as usual 2. The first character contains the type (which
   * player) and an age information (0,1,2). 3. The next four chars contain the
   * speed (high precision).
   * 
   * @param the
   *          player
   * @return the string representation
   */
  public String encodeTrainerPlayerSpeed(final Player player) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = player.getSpeed();
    int num = player.getNumber();
    if (num < 1) {
      return null;
    }
    if (num > 11) {
      return null;
    }
    if (!player.isFriend()) {
      num += 11;
    }
    final int age = 0; // Math.min(player.getAge(0),2);

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_PLAYER_SPD));

    // 2. player num and age
    this.str.append(NumberConverter.digits[(3 * num) + age]);

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxPlayerSpdDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    return this.str.toString();
  }

  /**
   * A Ball Position is encoded in the following way: 1. The first character
   * contains the id as usual 2. The first character contains the type (0) and
   * an age information (0,1,2). 3. The next four chars contain the position,
   * with an average error of 1cm!
   * 
   * @param the
   *          ball
   * @return - the string representation
   */
  public String encodeTrainerBallSpeed(final Ball ball) {

    this.str = new StringBuffer();

    // data to encode
    final Vektor v = ball.getSpeed();
    final int age = 0; // Math.min(ball.getAge(0),2);

    // create data:
    // 1. id
    this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_BALL_SPD));

    // 2. player num and age
    this.str.append(NumberConverter.digits[age]);

    // 3. vektor
    double angle = Vektor.CENTER.getAngleTo(v);
    // convert angle to 360 variant
    if (angle < 0) {
      angle = 360 + angle;
    }
    final double dist = Vektor.CENTER.getDistance(v);
    this.data_2[0] = angle / this.maxAngle;
    this.data_2[1] = dist / this.maxBallSpdDist;
    this.str.append(NumberConverter.convertToChars(this.data_2, 4));

    return this.str.toString();
  }

  /**
   * This method decodes the speed of a player. The results are set to <code>
   * this.number</code>,
   * <code>this.age</code> and <code>this.speed</code>.
   * 
   * @param str -
   *          the string-value!
   * @return true, if decoding was successful
   */
  public boolean decodePlayerSpdString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.PLAYER_SPD, str.charAt(0))) {
      return false;
    }

    // get number and age:
    this.number = this.sharedFrontValue[str.charAt(1)][2];
    this.age = this.sharedBackValue[str.charAt(1)][2];

    // get position
    this.cdata_3[0] = str.charAt(2);
    this.cdata_3[1] = str.charAt(3);
    this.cdata_3[2] = str.charAt(4);

    final double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxPlayerSpdDist;
    this.speed.pointAtPolar(dist, angle);

    return true;
  }

  /**
   * This method decodes the speed of the ball. The results are set to <code>
   * <code>this.age</code> and <code>this.speed</code>.
   * 
   * @param str -
   *            the string-value!
   * @return true, if decoding was successful
   */
  public boolean decodeBallSpdString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.BALL_SPD, str.charAt(0))) {
      return false;
    }
    this.age = this.sharedBackValue[str.charAt(1)][2];
    this.number = 0;

    // get position
    this.cdata_3[0] = str.charAt(2);
    this.cdata_3[1] = str.charAt(3);
    this.cdata_3[2] = str.charAt(4);

    final double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxBallSpdDist;
    this.speed.pointAtPolar(dist, angle);

    return true;
  }

  /**
   * This method decodes the speed of a player, which was encoded with higher
   * precision. The results are set to <code>this.number</code>, <code>
   * this.age</code>
   * and <code>this.speed</code>.
   * 
   * @param str -
   *          the string-value!
   * @return true, if the decoding was successful
   */
  public boolean decodeTrainerPlayerSpdString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.TRAINER_PLAYER_SPD, str.charAt(0))) {
      return false;
    }

    // get number and age:
    this.number = this.sharedFrontValue[str.charAt(1)][2];
    this.age = this.sharedBackValue[str.charAt(1)][2];

    // get position
    this.cdata_4[0] = str.charAt(2);
    this.cdata_4[1] = str.charAt(3);
    this.cdata_4[2] = str.charAt(4);
    this.cdata_4[3] = str.charAt(5);

    final double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxPlayerSpdDist;
    this.speed.pointAtPolar(dist, angle);

    return true;
  }

  /**
   * This method decodes the speed of the ball, which was encoded with high
   * precision. The results are set to <code>this.age</code> and
   * <code>this.speed</code>.
   * 
   * @param str -
   *          the string-value!
   * @return true, fi the decoding was successful
   */
  public boolean decodeTrainerBallSpdString(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.TRAINER_BALL_SPD, str.charAt(0))) {
      return false;
    }
    this.age = this.sharedBackValue[str.charAt(1)][2];
    this.number = 0;

    // get position
    this.cdata_4[0] = str.charAt(2);
    this.cdata_4[1] = str.charAt(3);
    this.cdata_4[2] = str.charAt(4);
    this.cdata_4[3] = str.charAt(5);

    final double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
    final double angle = result[0] * this.maxAngle;
    final double dist = result[1] * this.maxBallSpdDist;
    this.speed.pointAtPolar(dist, angle);

    return true;
  }

  /**
   * This method encodes the opponent types estimated by the trainer into a
   * string!
   * 
   * @param types -
   *          the type-values!
   * @return the string representation
   */
  public String encodeOpponentTypes(final int[] types) {

    this.str = new StringBuffer();
    this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_OPPTYPES));
    for (int i = 0; i < types.length; i++) {
      this.str.append(NumberConverter.digits[types[i]]);
    }
    return this.str.toString();
  }

  /**
   * This message decodes the player-types of the oppponents. The result is set
   * to the array <code>this.intArray</code>.
   * 
   * @param str -
   *          the string representation of this message
   * @return true, if decoding was successful.
   */
  public boolean decodeOpponentTypes(final String str) {

    if (!this.isIdOfMessageType(MESSAGES.TRAINER_OPPTYPES, str.charAt(0))) {
      return false;
    }
    this.intArray = new int[str.length() - 1];
    for (int i = 1; i < str.length(); i++) {
      this.intArray[i - 1] = this.valuesOfChars[str.charAt(i)];
    }
    return true;
  }

  /**
   * This method converts an integer to a character of the given char-alphabet
   * (NumberConverter). The higher the maxValue, the greater is the loss of
   * precision.
   * 
   * @param value -
   *          the value - has to be between min- and maxValue
   * @param minValue -
   *          the max-value
   * @param maxValue -
   *          the max-value
   * @return the corresponding character
   */
  private char convertIntToCharacter(int value,
      final int minValue,
      final int maxValue) {

    value = Math.max(value, minValue);
    value = Math.min(value, maxValue);
    final double range = maxValue - minValue;
    double v = value;
    v = v / range;
    this.data_1[0] = v;
    return (NumberConverter.convertToChars(this.data_1, 1))[0];
  }

  /**
   * This method converts an integer to a character of the given char-alphabet
   * (NumberConverter). The higher the maxValue, the greater is the loss of
   * precision.
   * 
   * @param value -
   *          the value - has to be between min- and maxValue
   * @param minValue -
   *          the max-value
   * @param maxValue -
   *          the max-value
   * @return the corresponding character
   */
  private char[] convertDoubleToCharsHP(double value,
      final double minValue,
      final double maxValue) {

    value = Math.max(value, minValue);
    value = Math.min(value, maxValue);
    final double range = maxValue - minValue;
    value = (value - minValue) / range;
    this.data_1[0] = value;
    return (NumberConverter.convertToChars(this.data_1, 2));
  }

  /**
   * This method calculates the length of the message in characters. Note that
   * the length is excluding the id, that is, the total number of chars for
   * sending is +1. If the result is -1, the message is not defined!
   * 
   * @param mess -
   *          the message type
   * @return the length!
   */
  public int getLengthOfMessage(final MESSAGES mess) {

    if (mess == null) {
      return -1;
    }
    return this.lengthOfMessage[mess.ordinal()];
  }

  /**
   * This message tests, if the given message type corresponds to the given
   * character-id!
   * 
   * @param mess -
   *          the message type
   * @param id -
   *          the id
   * @return true, if the message type belongs to the id
   */
  public boolean isIdOfMessageType(final MESSAGES mess,
      final char id) {

    if (mess == null) {
      return false;
    }
    else {
      return (this.getIdForMessageType(mess) == id);
    }
  }

  /**
   * This method retrieves the id of a giveb message-type!
   * 
   * @param mess -
   *          the message type
   * @return the corresponding character!
   */
  public char getIdForMessageType(final MESSAGES mess) {

    return NumberConverter.digits[mess.ordinal()];
  }

  /**
   * Method retrieves the age of the decoeded objects (ball or player). Use only
   * after decoding, else other values might be returned!
   * 
   * @return the decoded age (0-2)
   */
  public int getAge() {

    return this.age;
  }

  /**
   * Method retrieves the number of an object, which can be used to determine
   * the correct one from worldmodel: 0 - ball 1-11 own-team, 12-22 other team.
   * 
   * @return the number, which belongs to a player
   */
  public int getNumber() {

    return this.number;
  }

  /**
   * Retrieves the decoded position of a ball or player
   * 
   * @return the absolute vektor
   */
  public Vektor getPosition() {

    return this.position;
  }

  /**
   * Retrieves the absolute speed of a ball or player
   * 
   * @return the absolute vektor
   */
  public Vektor getSpeed() {

    return this.speed;
  }

  /**
   * Retrieves the stamina of the sender
   * 
   * @return the stamina value with a not so high precision.
   */
  public int getStamina() {

    return this.stamina;
  }

  /**
   * This method retrieves the cycles used for intercepting
   * 
   * @return the number of cycles.
   */
  public int getCycles() {

    return this.cycles;
  }

  /**
   * Retrieves the recovery of the sender
   * 
   * @return the recovery value
   */
  public double getRecovery() {

    return this.recovery;
  }

  /**
   * Retrieves the body angle of a player
   * 
   * @return the body angle of a player.
   */
  public double getBodyAngle() {

    return this.bodyAngle;
  }

  /**
   * @return Returns the numbers.
   */
  public int[] getNumbers() {

    return this.numbers;
  }

  /**
   * @return Returns the positions.
   */
  public Vektor[] getPositions() {

    return this.positions;
  }

  /**
   * This method extracts all messages that are inside a string by using the
   * length of each message and cutting the string into separated pieces!
   * 
   * @param str -
   *          the complete received string, which may contain multiple messages.
   * @param the
   *          current body-cycle, which is used to check whether a message is
   *          received in the same cycle or before.
   */
  public void generateMessages(final String str,
      final int bodyCycle) {

    if (this.bodyCycle != bodyCycle || bodyCycle == -1) {
      this.messageTypes.clear();
      this.messageContent.clear();
      this.bodyCycle = bodyCycle;
    }
    for (int i = 0; i < str.length();) {
      final char c = str.charAt(i);
      final MESSAGES m = (MESSAGES.values())[this.valuesOfChars[c]];
      this.messageTypes.add(m);
      this.messageContent
          .add(str.substring(i, i + this.lengthOfMessage[c] + 1));
      i += this.lengthOfMessage[c] + 1;
    }
  }

  /**
   * This method retrieves all received messagy types in a list!
   * 
   * @return all message-types, which were received!
   */
  public LinkedList<MESSAGES> getMessageTypes() {

    return this.messageTypes;
  }

  /**
   * This method encodes the message, which is part of the messageContent-List!
   * 
   * @param index -
   *          the inedex of the message!
   */
  public void decodeMessageNumber(final int index) {

    if (index < 0) {
      return;
    }
    if (index >= this.messageContent.size()) {
      return;
    }
    final MESSAGES mess = this.messageTypes.get(index);
    if (mess == MESSAGES.BALL_POS) {
      this.decodeBallPosString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.BALL_SPD) {
      this.decodeBallSpdString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.PLAYER_POS) {
      this.decodePlayerPosString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.PLAYER_POS_STAMINA) {
      this.decodePlayerPosAndStaminaString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.PLAYER_SPD) {
      this.decodePlayerSpdString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.MARK_PLAYER) {
      this.decodeMarkPlayer(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.STAMINA) {
      this.decodeStamina(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.KILL_PLAYER) {
      this.decodeKillPlayer(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.TRAINER_PLAYER_POS) {
      this.decodeTrainerPlayerPosString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.TRAINER_BALL_POS) {
      this.decodeTrainerBallPosString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.TRAINER_PLAYER_SPD) {
      this.decodeTrainerPlayerSpdString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.TRAINER_BALL_SPD) {
      this.decodeTrainerBallSpdString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.COMPLETE_BALL) {
      this.decodeCompleteBall(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.INTERCEPT_BALL) {
      this.decodeInterceptBall(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.COMPLETE_PLAYER) {
      this.decodeCompletePlayer(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.KICKED_BALL) {
      this.decodeKickedBall(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.DRIBBLE_BALL) {
      this.decodeDribbleBall(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.GOALIE_POS) {
      this.decodeGoaliePosString(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.RECOVERY) {
      this.decodeRecovery(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.TRAINER_OPPTYPES) {
      this.decodeOpponentTypes(this.messageContent.get(index));
    }
    else if (mess == MESSAGES.DRIBBLE_PPOS) {
      this.decodeDribblePlayer(this.messageContent.get(index));
    }
  }

  /**
   * This return the array of values, in which the opponent types are stored.
   * 
   * @return the array <code>this.intArray</code>.
   */
  public int[] getIntArray() {

    return this.intArray;
  }
}

/**
 * $Log:
 */
